package parsing.yago;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.sql.Date;
import java.util.Calendar;

import core.exceptions.ParserException;

abstract class YagoFileParser {

    protected static final String WIKICATEGORY = "wikicategory";

    /* directory where all YAGO files are placed: */
    private String _directory;

    protected YagoFileParser(String directory) {
        _directory = directory;
    }

    /* handles parsing of file (including opening file and so on) 
     * Call this method to parse the specific file! */
    protected final void parseFile() throws ParserException {

        String filename = getFileName();
        
        int progressStart = ParsingEnvironment.PROGRESSES_STARTING_POINT.get(filename);
        int progressDuartion = ParsingEnvironment.PROGRESSES_DURATIONS.get(filename);

        IParserProgress progress = ParsingEnvironment.getParserProgress();
        if (progress != null) {
        	progress.workingOnFile(filename);
        	progress.overallProgress(progressStart);
        }
        

        /* open file, send it to sub class to parse, handle closing
         * and exception handling here (sub class should handle its' own) */

        BufferedReader bufferedReader = null;
        int count = 0;
        try {
            /* open file for reading: */
            FileReader reader = new FileReader(_directory + filename);
            bufferedReader = new BufferedReader(reader);

            String line;
            boolean firstLine = true;

            /* iterate over file lines, send to sub class to parse each line: */

            while ((line = bufferedReader.readLine()) != null /*&& count < 500000*/) {
                String[] lineParts = splitAndParseLine(line);
                if (firstLine) {
                    firstLine = false;
                    /* check that the file format is as expected. Go by
                     * faith of induction (somewhat) and check only first line
                     */
                    if (!isLineFromFileOkay(lineParts)) {
                        throw new ParserException("YAGO file in illegal format", getFileName(), null);
                    }
                }
                /* send to subclass to handle parsing: */
                handleLineParsing(lineParts);

                if (++count % 1000000 == 0) {
                    if (progress != null) {
                    	float prog = (count / (float)getEstimatedFileLinesNumber());
                    	progress.fileProgress(prog * 100);
                    	progress.overallProgress(progressStart + (progressDuartion * prog));
                    			
                    }
                }
            }

        } catch (FileNotFoundException e) {
            throw new ParserException("YAGO file not found", getFileName(), e);
        } catch (ParserException e) {
            //do not handle this here, throw it back to user
            throw e;
        } catch (IOException e) {
            throw new ParserException("Error reading file at line " + count, getFileName(), e);
        } catch (Exception e) {
            throw new ParserException("Error parsing file at line " + count, getFileName(), e);
        } finally {
            /* make sure you close the reader!: */
            try {
                if (bufferedReader != null) {
                    bufferedReader.close();
                }
            } catch (IOException e) {
                //do nothing - file was read successfully even though
                //resource was not released :(
            }
        }

    }

    /* get Yago file name to open in this instance */
    protected abstract String getFileName();

    /* handle parsing of specific line  */
    protected abstract void handleLineParsing(String[] line);

    /* given a line already split to its' parts, check whether or not it is legal
     * (consistent with what you would expect the line in the file to be like) */
    protected abstract boolean isLineFromFileOkay(String[] line);
    
    protected abstract int getEstimatedFileLinesNumber();

    /* given a line from a file, returns an array of all it's parts in
     * human-readable form (no A_B_C, but A B C etc). */
    private String[] splitAndParseLine(String line) {
        //replace tabs with space
        line = line.replaceAll("\\s+", " ");
        //if we're left with white spaces in beginning or end, trim before splitting:
        line = line.trim();
        //split by said space:
        String[] parts = line.split(" ");
        //for each portion, replace "_" with space, and remove brackets:
        for (int i = 0; i < parts.length; i++) {
            parts[i] = parts[i].replace("<", "");
            parts[i] = parts[i].replace(">", "");
            parts[i] = parts[i].replace("_", " ");
        }
        return parts;
    }

    /* removes a wikicategory tag from the string */
    protected String removeWikicategory(String s) {
        return s.replace(WIKICATEGORY, "").trim();
    }

    protected Date parseDate(String s) {

        //date is in the form year.monthday

        String[] dateParts = s.split("\\.");
        if (dateParts.length != 2) {
            return null;
        }

        if (dateParts[0].length() != 4 || dateParts[1].length() != 4) {
            return null;
        }

        Date d = null;

        try {

            int year = Integer.parseInt(dateParts[0]);
            int month = Integer.parseInt(dateParts[1].substring(0, 2));
            int day = Integer.parseInt(dateParts[1].substring(2, 4));

            /* Day and Month unknown are marked in YAGO as zero, however Date
             * object doesn't recognize that as legal, so we'll twick the
             * data so that we'll change zero to one */
            if (month == 0) {
                month++;
            }
            if (day == 0) {
                day++;
            }

            Calendar.getInstance().set(year, month, day);
            d = new Date(Calendar.getInstance().get(Calendar.SECOND)*1000);

        } catch (NumberFormatException e) {
            //do nothing, we will return null
        }

        return d;
    }
}
